﻿using System;

namespace Utilities
{
    /// <summary>
    /// Generates a numeric field
    /// </summary>
    public static class NumberGenerator
    {
        public enum Types
        {
            Byte,
            SByte,
            Int16,
            UInt16,
            Int32,
            // UInt32 Not supported in random
            // Int64 Not supported in random
            // UInt64 Not supported in random
        }

        /// <summary>
        /// Generates a number whose value is constrained bythe NET type and the maximum number of digits
        /// </summary>
        /// <param name="type"></param>
        /// <param name="numberOfDigits"></param>
        /// <returns></returns>
        public static int Generate(Types type = Types.Int32, int numberOfDigits = 0)
        {
            DataInfo info = GetRange(type, numberOfDigits);
            return _random.Next(info.Min, info.Max);
        }

        public static int Generate(string typeName, int numberOfDigits = 0)
        {
            return Generate(ToType(typeName), numberOfDigits);
        }

        #region Helpers

        private static DataInfo GetRange(Types type, int numberOfDigits = 0)
        {
            DataInfo typeInfo = _typeInfos[(int)type];

            if (numberOfDigits <= 0)
            {
                return typeInfo;
            }

            // Correct the number of digits to be compatible with the data type
            if (numberOfDigits > typeInfo.Length)
            {
                numberOfDigits = typeInfo.Length;
            }

            DataInfo digitInfo = _digitInfos[numberOfDigits];

            return new DataInfo
            {
                Min = Math.Max(typeInfo.Min, digitInfo.Min),
                Max = Math.Min(typeInfo.Max, digitInfo.Max)
            };
        }

        private static Types ToType(string typeName)
        {
            switch (typeName)
            {
                case "System.Byte": return Types.Byte;
                case "System.SByte": return Types.SByte;
                case "System.Int16": return Types.Int16;
                case "System.UInt16": return Types.UInt16;
                default: return Types.Int32;
            }
        }

        #endregion

        struct DataInfo
        {
            internal int Min { get; set; }
            internal int Max { get; set; }
            internal int Length { get; set; }
        }

        private static readonly Random _random = new Random();

        private static readonly DataInfo[] _typeInfos = 
        {
            /* Byte */   new DataInfo { Min = byte.MinValue,   Max = byte.MaxValue,   Length = byte.MaxValue.ToString().Length},
            /* SByte */  new DataInfo { Min = sbyte.MinValue,  Max = sbyte.MaxValue,  Length = sbyte.MaxValue.ToString().Length},
            /* Int16 */  new DataInfo { Min = short.MinValue,  Max = short.MaxValue,  Length = short.MaxValue.ToString().Length},
            /* UInt16 */ new DataInfo { Min = ushort.MinValue, Max = ushort.MaxValue, Length = ushort.MaxValue.ToString().Length},
            /* Int32 */  new DataInfo { Min = int.MinValue,    Max = int.MaxValue,    Length = int.MaxValue.ToString().Length},
            ///* UInt32 */ new DataInfo { Min = uint.MinValue,   Max = uint.MaxValue,   Length = uint.MaxValue.ToString().Length},
            ///* Int64 */  new DataInfo { Min = long.MinValue,   Max = long.MaxValue,   Length = uint.MaxValue.ToString().Length},
            ///* UInt64 */ new DataInfo { Min = ulong.MinValue,  Max = ulong.MaxValue,  Length = uint.MaxValue.ToString().Length}
        };

        private static readonly DataInfo[] _digitInfos = 
        {
            new DataInfo { Min = 0,          Max = 0},
            new DataInfo { Min = 1,          Max = 9},
            new DataInfo { Min = 10,         Max = 99},
            new DataInfo { Min = 100,        Max = 999},
            new DataInfo { Min = 1000,       Max = 9999},
            new DataInfo { Min = 10000,      Max = 99999},
            new DataInfo { Min = 100000,     Max = 999999},
            new DataInfo { Min = 1000000,    Max = 9999999},
            new DataInfo { Min = 10000000,   Max = 99999999},
            new DataInfo { Min = 100000000,  Max = 999999999},
            new DataInfo { Min = 1000000000,  Max = int.MaxValue}
        };
    }
}
